package com.yeziji.security.component;

import cn.hutool.core.util.StrUtil;
import com.yeziji.common.CommonErrorMsg;
import com.yeziji.common.CommonResult;
import com.yeziji.common.CommonSymbol;
import com.yeziji.common.Platform;
import com.yeziji.common.context.OnlineContext;
import com.yeziji.constant.VariousStrPool;
import com.yeziji.exception.ApiException;
import com.yeziji.security.common.IUserDetails;
import com.yeziji.security.common.SecurityToken;
import com.yeziji.security.common.SecurityTokenContext;
import com.yeziji.security.service.DynamicSecurityAuth;
import com.yeziji.security.service.JwtAroundHandler;
import com.yeziji.security.utils.JwtUtils;
import com.yeziji.utils.NanoIdUtils;
import com.yeziji.utils.ResponseUtils;
import com.yeziji.utils.ServletUtils;
import com.yeziji.utils.expansion.Lists2;
import com.yeziji.utils.expansion.Opt2;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Nonnull;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;

/**
 * jwt 过滤器
 *
 * @author hwy
 * @since 2023/11/12 14:26
 **/
@Slf4j
public class JwtFilter extends OncePerRequestFilter implements HandlerInterceptor {
    @Resource
    private UserDetailsService userDetailsService;
    @Autowired(required = false)
    private DynamicSecurityAuth dynamicSecurityAuth;
    @Autowired(required = false)
    private List<JwtAroundHandler> jwtAroundHandlerList;

    @Override
    protected void doFilterInternal(@Nonnull HttpServletRequest request,
                                    @Nonnull HttpServletResponse response,
                                    @Nonnull FilterChain filterChain) {
        try {
            // 保存当前日志栈 id
            String mdc = NanoIdUtils.randomNotSymbolNaoId();
            MDC.put(VariousStrPool.System.TRACE_ID, mdc);
            OnlineContext.setMdc(mdc);
            OnlineContext.setPath(request.getRequestURI());
            // 获取平台信息
            Locale locale = Locale.getDefault();
            if (dynamicSecurityAuth != null) {
                Platform platform = dynamicSecurityAuth.getPlatform();
                if (platform != null) {
                    locale = Opt2.nullElse(dynamicSecurityAuth.getPlatform().getPlatformLocale(), Locale.CHINA);
                    OnlineContext.setPlatform(platform);
                }
            }
            // 请求指定的优先级最高(支持: zh_CN, zh_TW)
            String requestLocale = request.getHeader(VariousStrPool.HttpHeaders.LANGUAGE);
            if (StrUtil.isNotBlank(requestLocale)) {
                String[] localeSplit = requestLocale.split(CommonSymbol.BOTTOM_HORIZONTAL_BAR);
                final int localeSplitLen = localeSplit.length;
                if (localeSplitLen > 1) {
                    locale = new Locale(localeSplit[0], localeSplit[1]);
                } else {
                    locale = new Locale(localeSplit[0]);
                }
            }
            OnlineContext.setLocale(locale);
            OnlineContext.setIp(ServletUtils.getIpAddress(request));
            beforeHandler();
            // 校验令牌
            String authorization = request.getHeader(VariousStrPool.HttpHeaders.AUTHORIZATION);
            if (StrUtil.isNotBlank(authorization)) {
                // 不为空先判断是否已经遗弃
                if (SecurityTokenContext.isDeprecated(authorization)) {
                    ResponseUtils.writeJson(response, CommonResult.failed(CommonErrorMsg.TOKEN_IS_EXPIRED));
                    return;
                }
                // 查看本地是否有上下文记录过当前令牌
                SecurityToken securityToken = SecurityTokenContext.get(authorization);
                // 如果本地没有存储上下文信息，那么就可能是第一次登录本地系统
                if (securityToken == null) {
                    String refreshAuthorization = request.getHeader(VariousStrPool.HttpHeaders.REFRESH_AUTHORIZATION);
                    // 令牌已经过期
                    if (JwtUtils.isExpired(authorization)) {
                        // 判断是否存在刷新令牌，如果存在并且没过期，那就重新生成新的令牌对象
                        if (StrUtil.isNotBlank(refreshAuthorization) && !JwtUtils.isExpired(refreshAuthorization)) {
                            securityToken = JwtUtils.renewalTokenByRefreshToken(refreshAuthorization);
                            SecurityTokenContext.put(securityToken.getOriginalToken(), securityToken);
                        }
                        // 完全过期
                        else {
                            ResponseUtils.writeJson(response, CommonResult.failed(CommonErrorMsg.TOKEN_IS_EXPIRED));
                            return;
                        }
                    }
                    // 沒过期或可能第一次存储上下文信息
                    else {
                        // 如果不存在刷新令牌，就生成一个放入响应头当中
                        if (StrUtil.isBlank(refreshAuthorization)) {
                            refreshAuthorization = JwtUtils.copyTokenAndNewExpired(authorization, JwtUtils.ADDITIONAL_EXPIRED);
                            response.setHeader(VariousStrPool.HttpHeaders.REFRESH_AUTHORIZATION, refreshAuthorization);
                        }
                        // 重新赋值并塞回上下文
                        securityToken = SecurityToken.builder()
                                .originalToken(authorization)
                                .refreshToken(refreshAuthorization)
                                .build();
                        SecurityTokenContext.put(authorization, securityToken);
                    }
                }
                // 完全过期就直接返回
                else if (securityToken.completelyExpired()) {
                    ResponseUtils.writeJson(response, CommonResult.failed(CommonErrorMsg.TOKEN_IS_EXPIRED));
                    return;
                }
                // 校验 token
                else if (this.checkSecurityToken(securityToken)) {
                    // token 被刷新时，change 会为 true，那么这时就更新 token
                    if (securityToken.isChange()) {
                        SecurityTokenContext.update(authorization, securityToken);
                        response.setHeader(VariousStrPool.HttpHeaders.AUTHORIZATION, securityToken.getOriginalToken());
                        response.setHeader(VariousStrPool.HttpHeaders.REFRESH_AUTHORIZATION, securityToken.getRefreshToken());
                    }
                }
                // 绑定当前用户权限信息上下文
                IUserDetails userDetails = (IUserDetails) userDetailsService.loadUserByUsername(JwtUtils.getUserOnlineBaseByToken(authorization).getUsername());
                if (userDetails != null) {
                    OnlineContext.setOnlineUserDetails(userDetails);
                    SecurityContextHolder.getContext()
                            .setAuthentication(new UsernamePasswordAuthenticationToken(OnlineContext.getUserId(), null, userDetails.getAuthorities()));
                }
                // 保存当前在线信息
                OnlineContext.setToken(securityToken.getOriginalToken());
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            log.error("JwtFilter: Do Error: {}", e.getMessage(), e);
            exceptionHandler();
            if (e instanceof ApiException) {
                ResponseUtils.writeJson(response, CommonResult.failed(e.getMessage()));
            } else {
                ResponseUtils.writeJson(response, CommonResult.failed(CommonErrorMsg.SYSTEM_ERROR));
            }
        } finally {
            completeHandler();
            MDC.clear();
            OnlineContext.clear();
        }
    }

    /**
     * 处理 jwt 前的操作
     */
    private void beforeHandler() {
        handler(JwtAroundHandler::onBefore);
    }

    /**
     * 处理 jwt 异常时操作
     */
    private void exceptionHandler() {
        handler(JwtAroundHandler::onException);
    }

    /**
     * 处理 jwt finally 操作
     */
    private void completeHandler() {
        handler(JwtAroundHandler::onComplete);
    }

    /**
     * 处理调用 JwtAroundHandler 通用方法
     * <p>
     * 优先执行异步方法, 然后单独执行同步方法
     * </p>
     *
     * @param consumer 消费行为
     */
    private void handler(Consumer<JwtAroundHandler> consumer) {
        Lists2.isNotEmptyThen(jwtAroundHandlerList, handlers -> handlers.forEach(consumer));
    }

    /**
     * 检查 security token
     *
     * @param securityToken 检查的加密令牌对象
     * @return {@link Boolean} 检查结果
     */
    private boolean checkSecurityToken(SecurityToken securityToken) {
        if (securityToken == null || securityToken.completelyExpired()) {
            return false;
        }

        if (!securityToken.expired()) {
            return true;
        }
        // 初始 token 过期了，但是刷新 token 没过期，赋值全新的 token
        else if (!securityToken.refreshExpired()) {
            SecurityToken newSecurityToken = JwtUtils.renewalTokenByRefreshToken(securityToken.getRefreshToken());
            securityToken.copy(newSecurityToken);
            securityToken.setChange(true);
            return true;
        }

        return false;
    }
}
